package com.ivo.util;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;

import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;

/**
 * This class is extremely useful for loading menuItems and classes in a fault tolerant manner
 * that works across different applications servers.
 *
 * It has come out of many months of frustrating use of multiple application servers at Atlassian,
 * please don't change things unless you're sure they're not going to break in one server or another!
 *
 * @author Hani
 */
public class ResourceUtil
{
	/** Pseudo URL prefix for loading from the class path: "classpath:" */
	public static final String CLASSPATH_URL_PREFIX = "classpath:";

	/** URL prefix for loading from the file system: "file:" */
	public static final String FILE_URL_PREFIX = "file:";

	/** URL protocol for a file in the file system: "file" */
	public static final String URL_PROTOCOL_FILE = "file";

	//~ Methods ////////////////////////////////////////////////////////////////
	/**
	* This is a convenience method to load a resource as a stream.
	*
	* The algorithm used to find the resource is given in getResource()
	*
	* @param resourceName The name of the resource to load
	* @param callingClass The Class object of the calling object
	*/
	public static InputStream getResourceAsStream(String resourceName) throws FileNotFoundException
	{
		URL url = getURL( resourceName );

		try {
			return ( url != null ) ? url.openStream() : null;
		} catch( IOException e ) {
			return null;
		}
	}

	/**
	 * Return whether the given resource location is a URL:
	 * either a special "classpath" pseudo URL or a standard URL.
	 * @see #CLASSPATH_URL_PREFIX
	 * @see java.net.URL
	 */
	public static boolean isUrl(String resourceLocation)
	{
		if( resourceLocation == null ) {
			return false;
		}
		if( resourceLocation.startsWith( CLASSPATH_URL_PREFIX ) ) {
			return true;
		}
		try {
			new URL( resourceLocation );
			return true;
		} catch( MalformedURLException ex ) {
			return false;
		}
	}

	/**
	 * Resolve the given resource location to a <code>java.net.URL</code>.
	 * <p>Does not check whether the URL actually exists; simply returns
	 * the URL that the given location would correspond to.
	 * @param resourceLocation the resource location to resolve: either a
	 * "classpath:" pseudo URL, a "file:" URL, or a plain file path
	 * @return a corresponding URL object
	 * @throws FileNotFoundException if the resource cannot be resolved to a URL
	 */
	public static URL getURL(String resourceLocation) throws FileNotFoundException
	{
		Assert.notNull( resourceLocation, "MenuItem location must not be null" );
		if( resourceLocation.startsWith( CLASSPATH_URL_PREFIX ) ) {
			String path = resourceLocation.substring( CLASSPATH_URL_PREFIX.length() );
			URL url = ClassUtils.getDefaultClassLoader().getResource( path );
			if( url == null ) {
				String description = "class path resource [" + path + "]";
				throw new FileNotFoundException(
						description + " cannot be resolved to URL because it does not exist" );
			}
			return url;
		}
		try {
			// try URL
			return new URL( resourceLocation );
		} catch( MalformedURLException ex ) {
			// no URL -> treat as file path
			try {
				return new URL( FILE_URL_PREFIX + resourceLocation );
			} catch( MalformedURLException ex2 ) {
				throw new FileNotFoundException( "MenuItem location [" + resourceLocation +
						"] is neither a URL not a well-formed file path" );
			}
		}
	}

	/**
	 * Resolve the given resource location to a <code>java.io.File</code>,
	 * i.e. to a file in the file system.
	 * <p>Does not check whether the fil actually exists; simply returns
	 * the File that the given location would correspond to.
	 * @param resourceLocation the resource location to resolve: either a
	 * "classpath:" pseudo URL, a "file:" URL, or a plain file path
	 * @return a corresponding File object
	 * @throws FileNotFoundException if the resource cannot be resolved to
	 * a file in the file system
	 */
	public static File getFile(String resourceLocation) throws FileNotFoundException
	{
		Assert.notNull( resourceLocation, "MenuItem location must not be null" );
		if( resourceLocation.startsWith( CLASSPATH_URL_PREFIX ) ) {
			String path = resourceLocation.substring( CLASSPATH_URL_PREFIX.length() );
			String description = "class path resource [" + path + "]";
			URL url = ClassUtils.getDefaultClassLoader().getResource( path );
			if( url == null ) {
				throw new FileNotFoundException(
						description + " cannot be resolved to absolute file path " +
								"because it does not reside in the file system" );
			}
			return getFile( url, description );
		}
		try {
			// try URL
			return getFile( new URL( resourceLocation ) );
		} catch( MalformedURLException ex ) {
			// no URL -> treat as file path
			return new File( resourceLocation );
		}
	}

	/**
	 * Resolve the given resource URL to a <code>java.io.File</code>,
	 * i.e. to a file in the file system.
	 * @param resourceUrl the resource URL to resolve
	 * @return a corresponding File object
	 * @throws FileNotFoundException if the URL cannot be resolved to
	 * a file in the file system
	 */
	public static File getFile(URL resourceUrl) throws FileNotFoundException
	{
		return getFile( resourceUrl, "URL" );
	}

	/**
	 * Resolve the given resource URL to a <code>java.io.File</code>,
	 * i.e. to a file in the file system.
	 * @param resourceUrl the resource URL to resolve
	 * @param description a description of the original resource that
	 * the URL was created for (for example, a class path location)
	 * @return a corresponding File object
	 * @throws FileNotFoundException if the URL cannot be resolved to
	 * a file in the file system
	 */
	public static File getFile(URL resourceUrl, String description) throws FileNotFoundException
	{
		Assert.notNull( resourceUrl, "MenuItem URL must not be null" );
		if( !URL_PROTOCOL_FILE.equals( resourceUrl.getProtocol() ) ) {
			throw new FileNotFoundException(
					description + " cannot be resolved to absolute file path " +
							"because it does not reside in the file system: " + resourceUrl );
		}
		return new File( resourceUrl.getFile() );
	}

	/**
	* Load a class with a given name.
	*
	* It will try to load the class in the following order:
	* <ul>
	*  <li>From Thread.currentThread().getContextClassLoader()
	*  <li>Using the basic Class.forName()
	*  <li>From ClassLoaderUtil.class.getClassLoader()
	*  <li>From the callingClass.getClassLoader()
	* </ul>
	*
	* @param className The name of the class to load
	* @param callingClass The Class object of the calling object
	* @throws ClassNotFoundException If the class cannot be found anywhere.
	*/
	public static Class loadClass(String className, Class callingClass) throws ClassNotFoundException
	{
		try {
			return Thread.currentThread().getContextClassLoader().loadClass( className );
		} catch( ClassNotFoundException e ) {
			try {
				return Class.forName( className );
			} catch( ClassNotFoundException ex ) {
				try {
					return ResourceUtil.class.getClassLoader().loadClass( className );
				} catch( ClassNotFoundException exc ) {
					return callingClass.getClassLoader().loadClass( className );
				}
			}
		}
	}
}
